/* * @copyright 2010 Evan Leybourn * @license GNU General Public License * * This file is part of Book Catalogue. * * Book Catalogue is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Book Catalogue is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Book Catalogue. If not, see <http://www.gnu.org/licenses/>. */ package com.eleybourn.bookcatalogue; import java.util.ArrayList; import java.util.Iterator; import net.philipwarner.taskqueue.QueueManager; import android.app.AlertDialog; import android.app.ExpandableListActivity; import android.app.SearchManager; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.database.Cursor; import android.database.sqlite.SQLiteCursor; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.AdapterView.OnItemSelectedListener; import android.widget.ArrayAdapter; import android.widget.ExpandableListAdapter; import android.widget.ExpandableListView; import android.widget.ExpandableListView.ExpandableListContextMenuInfo; import android.widget.ExpandableListView.OnGroupCollapseListener; import android.widget.ExpandableListView.OnGroupExpandListener; import android.widget.ImageView; import android.widget.RadioButton; import android.widget.RadioGroup; import android.widget.ResourceCursorTreeAdapter; import android.widget.ScrollView; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import com.eleybourn.bookcatalogue.booklist.BooklistPreferencesActivity; import com.eleybourn.bookcatalogue.dialogs.StandardDialogs; import com.eleybourn.bookcatalogue.goodreads.GoodreadsManager; import com.eleybourn.bookcatalogue.goodreads.GoodreadsManager.Exceptions.NetworkException; import com.eleybourn.bookcatalogue.goodreads.SendOneBookTask; import com.eleybourn.bookcatalogue.utils.Logger; import com.eleybourn.bookcatalogue.utils.SimpleTaskQueue; import com.eleybourn.bookcatalogue.utils.Utils; import com.eleybourn.bookcatalogue.utils.ViewTagger; import com.eleybourn.bookcatalogue.widgets.FastScrollExpandableListView; /* * A book catalogue application that integrates with Google Books. */ public class BookCatalogueClassic extends ExpandableListActivity { // Target size of a thumbnail in a list (bbox dim) private static final int LIST_THUMBNAIL_SIZE=60; private CatalogueDBAdapter mDbHelper; private static final int SORT_BY_AUTHOR_EXPANDED = MenuHandler.FIRST + 1; private static final int SORT_BY_AUTHOR_COLLAPSED = MenuHandler.FIRST + 2; private static final int SORT_BY = MenuHandler.FIRST + 3; private static final int DELETE_ID = MenuHandler.FIRST + 7; private static final int EDIT_BOOK = MenuHandler.FIRST + 10; private static final int EDIT_BOOK_NOTES = MenuHandler.FIRST + 11; private static final int EDIT_BOOK_FRIENDS = MenuHandler.FIRST + 12; private static final int DELETE_SERIES_ID = MenuHandler.FIRST + 15; private static final int EDIT_AUTHOR_ID = MenuHandler.FIRST + 16; private static final int EDIT_SERIES_ID = MenuHandler.FIRST + 17; private static final int EDIT_BOOK_SEND_TO_GR = MenuHandler.FIRST + 19; private String bookshelf = ""; private ArrayAdapter<String> spinnerAdapter; private Spinner mBookshelfText; private SharedPreferences mPrefs; public int sort = 0; private static final int SORT_AUTHOR = 0; private static final int SORT_TITLE = 1; private static final int SORT_SERIES = 2; private static final int SORT_LOAN = 3; private static final int SORT_UNREAD = 4; private static final int SORT_GENRE = 5; private static final int SORT_AUTHOR_GIVEN = 6; private static final int SORT_AUTHOR_ONE = 7; private static final int SORT_PUBLISHED = 8; private ArrayList<Integer> currentGroup = new ArrayList<Integer>(); private Long mLoadingGroups = 0L; private boolean collapsed = false; /** Utils object; we need an instance for cover retrieval because it uses a DB connection * that we do not want to make static. */ private Utils mUtils = new Utils(); private SimpleTaskQueue mTaskQueue = null; /* Side-step a bug in HONEYCOMB. It seems that startManagingCursor() in honeycomb causes * child-list cursors for ExpanadableList objects to be closed prematurely. So we seem to have * to roll our own...see http://osdir.com/ml/Android-Developers/2011-03/msg02605.html. */ private ArrayList<Cursor> mManagedCursors = new ArrayList<Cursor>(); @Override public void startManagingCursor(Cursor c) { synchronized(mManagedCursors) { if (!mManagedCursors.contains(c)) mManagedCursors.add(c); } } @Override public void stopManagingCursor(Cursor c) { synchronized(mManagedCursors) { try { mManagedCursors.remove(c); } catch (Exception e) { // Don;t really care if it's called more than once. } } } private void destroyManagedCursors() { synchronized(mManagedCursors) { for (Cursor c : mManagedCursors) { try { c.close(); } catch (Exception e) { // Don;t really care if it's called more than once or fails. } } mManagedCursors.clear(); } } private String justAdded = ""; private String search_query = ""; // These are the states that get saved onPause private static final String STATE_SORT = "state_sort"; //private static final String STATE_BOOKSHELF = "state_bookshelf"; private static final String STATE_CURRENT_GROUP_COUNT = "state_current_group_count"; private static final String STATE_CURRENT_GROUP = "state_current_group"; /** * Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { //check which strings.xml file is currently active if (!getString(R.string.system_app_name).equals(Utils.APP_NAME)) { throw new NullPointerException(); } bookshelf = getString(R.string.all_books); try { super.onCreate(savedInstanceState); // In V4.0 the startup activity is StartupActivity, but we need to deal with old icons. // So we check the intent. // TODO: Consider renaming 'BookCatalogue' activity to 'BookCatalogueClassic' and creating a dummy BookCatalgue activity stub to avoid this check if ( ! StartupActivity.hasBeenCalled() ) { // The startup activity has NOT been called; this may be because of a restart after FC, in which case the action may be null, or may be valid Intent i = getIntent(); final String action = i.getAction(); if (action != null && action.equals("android.intent.action.MAIN") && i.hasCategory("android.intent.category.LAUNCHER")) { // This is a startup for the main application, so defer it to the StartupActivity System.out.println("Old shortcut detected, redirecting"); i = new Intent(this.getApplicationContext(), StartupActivity.class); startActivity(i); finish(); return; } } // Extract the sort type from the bundle. getInt will return 0 if there is no attribute // sort (which is exactly what we want) try { mPrefs = getSharedPreferences("bookCatalogue", MODE_PRIVATE); sort = mPrefs.getInt(STATE_SORT, sort); bookshelf = mPrefs.getString(BooksOnBookshelf.PREF_BOOKSHELF, bookshelf); loadCurrentGroup(); } catch (Exception e) { Logger.logError(e); } // This sets the search capability to local (application) search setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); setContentView(R.layout.list_authors); mDbHelper = new CatalogueDBAdapter(this); mDbHelper.open(); // Did the user search Intent intent = getIntent(); if (Intent.ACTION_SEARCH.equals(intent.getAction())) { // Return the search results instead of all books (for the bookshelf) search_query = intent.getStringExtra(SearchManager.QUERY).trim(); } else if (Intent.ACTION_VIEW.equals(intent.getAction())) { // Handle a suggestions click (because the suggestions all use ACTION_VIEW) search_query = intent.getDataString(); } if (search_query == null || search_query.equals(".")) { search_query = ""; } bookshelf(); //fillData(); registerForContextMenu(getExpandableListView()); } catch (Exception e) { Logger.logError(e); // Need to finish this activity, otherwise we end up in an invalid state. finish(); } } /** * Setup the bookshelf spinner. This function will also call fillData when * complete having loaded the appropriate bookshelf. */ private void bookshelf() { // Setup the Bookshelf Spinner mBookshelfText = (Spinner) findViewById(R.id.bookshelf_name); spinnerAdapter = new ArrayAdapter<String>(this, R.layout.spinner_frontpage); spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mBookshelfText.setAdapter(spinnerAdapter); // Add the default All Books bookshelf int pos = 0; int bspos = pos; spinnerAdapter.add(getString(R.string.all_books)); pos++; Cursor bookshelves = mDbHelper.fetchAllBookshelves(); if (bookshelves.moveToFirst()) { do { String this_bookshelf = bookshelves.getString(1); if (this_bookshelf.equals(bookshelf)) { bspos = pos; } pos++; spinnerAdapter.add(this_bookshelf); } while (bookshelves.moveToNext()); } bookshelves.close(); // close the cursor // Set the current bookshelf. We use this to force the correct bookshelf after // the state has been restored. mBookshelfText.setSelection(bspos); /** * This is fired whenever a bookshelf is selected. It is also fired when the * page is loaded with the default (or current) bookshelf. */ mBookshelfText.setOnItemSelectedListener(new OnItemSelectedListener() { public void onItemSelected(AdapterView<?> parentView, View view, int position, long id) { String new_bookshelf = spinnerAdapter.getItem(position); if (position == 0) { new_bookshelf = ""; } if (!new_bookshelf.equals(bookshelf)) { currentGroup = new ArrayList<Integer>(); } bookshelf = new_bookshelf; // save the current bookshelf into the preferences SharedPreferences.Editor ed = mPrefs.edit(); ed.putString(BooksOnBookshelf.PREF_BOOKSHELF, bookshelf); ed.commit(); fillData(); } public void onNothingSelected(AdapterView<?> parentView) { // Do Nothing } }); ImageView mBookshelfDown = (ImageView) findViewById(R.id.bookshelf_down); mBookshelfDown.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mBookshelfText.performClick(); return; } }); TextView mBookshelfNum = (TextView) findViewById(R.id.bookshelf_num); mBookshelfNum.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { mBookshelfText.performClick(); return; } }); } /* * Class that handles list/view specific initializations etc. * The member variable mViewManager is set early in object * initialization of the containing class. * * All child views are assumed to have books in them and a single * method call is made to bind a child view. */ private abstract class ViewManager { protected int mGroupIdColumnIndex; protected int mLayout = -1; // Top level resource I protected int mChildLayout = -1; // Child resource ID protected Cursor mCursor = null; // Top level cursor protected String[] mFrom = null; // Source fields for top level resource protected int[] mTo = null; // Dest field resource IDs for top level // Methods to 'get' list/view related items public int getLayout() { return mLayout; }; public int getLayoutChild() { return mChildLayout; }; public Cursor getCursor() { if (mCursor == null) { newGroupCursor(); BookCatalogueClassic.this.startManagingCursor(mCursor); } return mCursor; }; abstract public Cursor newGroupCursor(); public String[] getFrom() { return mFrom; }; public int[] getTo() { return mTo; }; /** * Method to return the group cursor column that contains text that can be used * to derive the section name used by the FastScroller overlay. * * @return column number */ abstract public int getSectionNameColum(); /** * Get a cursor to retrieve list of children; must be a database cursor * and will be converted to a CursorSnapshotCursor */ public abstract SQLiteCursor getChildrenCursor(Cursor groupCursor); public BasicBookListAdapter newAdapter(Context context) { return new BasicBookListAdapter(context); } /** * Record to store the details of a TextView in the list items. */ private class TextViewInfo { boolean show; TextView view; } /** * Record to store the details of a ImafeView in the list items. */ private class ImageViewInfo { boolean show; ImageView view; } /** * Record to implement the 'holder' model for the list. */ private class BookHolder { TextViewInfo author = new TextViewInfo(); TextViewInfo title = new TextViewInfo(); TextViewInfo series = new TextViewInfo(); ImageViewInfo image = new ImageViewInfo(); TextViewInfo publisher = new TextViewInfo(); ImageViewInfo read = new ImageViewInfo(); } /** * Adapter for the the expandable list of books. Uses ViewManager to manage * cursor. * * @author Philip Warner */ public class BasicBookListAdapter extends ResourceCursorTreeAdapter implements android.widget.SectionIndexer { /** A local Inflater for convenience */ LayoutInflater mInflater; /** * * Pass the parameters directly to the overridden function and * create an Inflater for use later. * * Note: It would be great to pass a ViewManager to the constructor * as an instance variable, but the 'super' initializer calls * getChildrenCursor which needs the ViewManager...which can not be set * before the call to 'super'...so we use an instance variable in the * containing class. * * @param context */ private int[] mFromCols = null; private final int[] mToIds; public BasicBookListAdapter(Context context) { super(context, ViewManager.this.getCursor(), ViewManager.this.getLayout(), ViewManager.this.getLayoutChild()); mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); mToIds = ViewManager.this.getTo(); } /** * Bind the passed 'from' text fields to the 'to' fields. * * If anything more fancy is needed, we probably need to implement it * in the subclass. */ @Override protected void bindGroupView(View view, Context context, Cursor cursor, boolean isExpanded) { if (mFromCols == null) { String [] fromNames = ViewManager.this.getFrom(); mFromCols = new int[fromNames.length]; for(int i = 0 ; i < fromNames.length; i++ ) mFromCols[i] = cursor.getColumnIndex(fromNames[i]); } for (int i = 0; i < mToIds.length; i++) { View v = view.findViewById(mToIds[i]); if (v != null) { String text = cursor.getString(mFromCols[i]); if (text == null) { text = ""; } if (v instanceof TextView) { ((TextView) v).setText(text); } else { throw new IllegalStateException("Can only bind to TextView for groups"); } } } } /** * Override the getChildrenCursor. This runs the SQL to extract the titles per author */ @Override protected Cursor getChildrenCursor(Cursor groupCursor) { if (mDbHelper == null) // We are terminating return null; // Get the DB cursor SQLiteCursor children = ViewManager.this.getChildrenCursor(groupCursor); // // Make a snapshot of it to avoid keeping potentially hundreds of cursors open // // If we ever set Android 2.0 as minimum, do this... // CursorSnapshotCursor csc; // if (children instanceof BooksCursor) { // csc = new BooksSnapshotCursor(children); // } else { // csc = new CursorSnapshotCursor(children); // } // children.close(); // BookCatalogue.this.startManagingCursor(csc); // TODO FIND A BETTER CURSOR MANAGEMENT SOLUTION! // THIS CAUSES CRASH IN HONEYCOMB when viewing book details then clicking 'back', so we have // overridden startManagingCursor to only close cursors in onDestroy(). BookCatalogueClassic.this.startManagingCursor(children); return children; } /** * Setup the related info record based on actual View contents */ private void initViewInfo(View v, TextViewInfo info, int id, String setting) { info.show = mPrefs.getBoolean(setting, true); info.view = (TextView) v.findViewById(id); if (!info.show) { if (info.view != null) info.view.setVisibility(View.GONE); } else { info.show = (info.view != null); if (info.show) info.view.setVisibility(View.VISIBLE); } } /** * Setup the related info record based on actual View contents */ private void initViewInfo(View v, ImageViewInfo info, int id, String setting) { info.show = mPrefs.getBoolean(setting, true); info.view = (ImageView) v.findViewById(id); if (!info.show) { info.view.setVisibility(View.GONE); } else { info.show = (info.view != null); if (info.show) info.view.setVisibility(View.VISIBLE); } } /** * Override the newChildView method so we can implement a holder model to improve performance. */ @Override public View newChildView(Context context, Cursor cursor, boolean isLastChild, ViewGroup parent) { View v = mInflater.inflate(ViewManager.this.getLayoutChild(), parent, false); BookHolder holder = new BookHolder(); initViewInfo(v, holder.author, R.id.row_author, FieldVisibility.prefix + CatalogueDBAdapter.KEY_AUTHOR_NAME); initViewInfo(v, holder.title, R.id.row_title, FieldVisibility.prefix + CatalogueDBAdapter.KEY_TITLE); initViewInfo(v, holder.image, R.id.row_image_view, FieldVisibility.prefix + "thumbnail"); initViewInfo(v, holder.publisher, R.id.row_publisher, FieldVisibility.prefix + CatalogueDBAdapter.KEY_PUBLISHER); initViewInfo(v, holder.read, R.id.row_read_image_view, FieldVisibility.prefix + "read"); initViewInfo(v, holder.series, R.id.row_series, FieldVisibility.prefix + CatalogueDBAdapter.KEY_SERIES_NAME); ViewTagger.setTag(v, R.id.TAG_HOLDER, holder); return v; } /** * Rather than having setText/setImage etc, or using messy from/to fields, * we just bind child views using a Holder object and the cursor RowView. */ @Override protected void bindChildView(View view, Context context, Cursor origCursor, boolean isLastChild) { BookHolder holder = (BookHolder) ViewTagger.getTag(view, R.id.TAG_HOLDER); final BooksCursor snapshot = (BooksCursor) origCursor; final BooksRowView rowView = snapshot.getRowView(); if (holder.author.show) holder.author.view.setText(rowView.getPrimaryAuthorName()); if (holder.title.show) holder.title.view.setText(rowView.getTitle()); if (holder.image.show) { //CatalogueDBAdapter.fetchThumbnailIntoImageView(cursor.getId(),holder.image.view, LIST_THUMBNAIL_SIZE, LIST_THUMBNAIL_SIZE, true, mTaskQueue); mUtils.fetchBookCoverIntoImageView(holder.image.view, LIST_THUMBNAIL_SIZE, LIST_THUMBNAIL_SIZE, true, rowView.getBookUuid(), BooklistPreferencesActivity.isThumbnailCacheEnabled(), BooklistPreferencesActivity.isBackgroundThumbnailsEnabled()); } if (holder.read.show) { int read; try { read = rowView.getRead(); } catch (Exception e) { read = 0; } if (read == 1) { holder.read.view.setImageResource(R.drawable.btn_check_buttonless_on); } else { holder.read.view.setImageResource(R.drawable.btn_check_buttonless_off); } } if (holder.series.show) { String series = rowView.getSeries(); if (sort == SORT_SERIES || series.length() == 0) { holder.series.view.setText(""); } else { holder.series.view.setText("[" + series + "]"); } } if (holder.publisher.show) holder.publisher.view.setText(rowView.getPublisher()); } /** * Utility routine to regenerate the groups cursor using the enclosing ViewManager. */ private void regenGroups() { setGroupCursor(newGroupCursor()); notifyDataSetChanged(); // Reset the scroller, just in case FastScrollExpandableListView fselv = (FastScrollExpandableListView)BookCatalogueClassic.this.getExpandableListView(); fselv.setFastScrollEnabled(false); fselv.setFastScrollEnabled(true); } /** * Get section names for the FastScroller. We just return all the groups. */ @Override public Object[] getSections() { // Get the group cursor and save its position Cursor c = ViewManager.this.getCursor(); int savedPosition = c.getPosition(); // Create the string array int count = c.getCount(); String[] sections = new String[count]; c.moveToFirst(); // Get the column number from the cursor column we use for sections. int sectionCol = ViewManager.this.getSectionNameColum(); // Populate the sections for(int i = 0; i < count; i++) { sections[i] = c.getString(sectionCol); c.moveToNext(); } // Reset cursor and return c.moveToPosition(savedPosition); return sections; } /** * Passed a section number, return the flattened position in the list */ @Override public int getPositionForSection(int section) { return getExpandableListView().getFlatListPosition(ExpandableListView.getPackedPositionForGroup(section)); } /** * Passed a flattened position in the list, return the section number */ @Override public int getSectionForPosition(int position) { final ExpandableListView list = getExpandableListView(); long packedPos = list.getExpandableListPosition(position); return ExpandableListView.getPackedPositionGroup(packedPos); } } } /* * ViewManager for sorting by Title */ private class TitleViewManager extends ViewManager { TitleViewManager() { mLayout = R.layout.row_authors; mChildLayout = R.layout.row_books; mFrom = new String[]{CatalogueDBAdapter.KEY_ROWID}; mTo = new int[]{R.id.row_family}; } public SQLiteCursor getChildrenCursor(Cursor groupCursor) { if (search_query.equals("")) { return mDbHelper.fetchAllBooksByChar(groupCursor.getString(mGroupIdColumnIndex), bookshelf, ""); } else { return mDbHelper.searchBooksByChar(search_query, groupCursor.getString(mGroupIdColumnIndex), bookshelf); } } @Override public Cursor newGroupCursor() { if (search_query.equals("")) { // Return all books (for the bookshelf) mCursor = mDbHelper.fetchAllBookChars(bookshelf); } else { // Return the search results instead of all books (for the bookshelf) mCursor = mDbHelper.searchBooksChars(search_query, bookshelf); } mGroupIdColumnIndex = mCursor.getColumnIndex(CatalogueDBAdapter.KEY_ROWID); return mCursor; } @Override public int getSectionNameColum() { return mCursor.getColumnIndex(CatalogueDBAdapter.KEY_ROWID); } } /* * ViewManager for sorting by Author */ private class AuthorViewManager extends ViewManager { AuthorViewManager() { mLayout = R.layout.row_authors; mChildLayout = R.layout.row_authors_books; mFrom = new String[]{CatalogueDBAdapter.KEY_AUTHOR_FORMATTED}; mTo = new int[]{R.id.row_family}; } public SQLiteCursor getChildrenCursor(final Cursor groupCursor) { return mDbHelper.fetchAllBooksByAuthor(groupCursor.getInt(mGroupIdColumnIndex), bookshelf, search_query, false); } @Override public Cursor newGroupCursor() { if (search_query.equals("")) { // Return all books for the given bookshelf mCursor = mDbHelper.fetchAllAuthors(bookshelf); } else { // Return the search results instead of all books (for the bookshelf) mCursor = mDbHelper.searchAuthors(search_query, bookshelf); } mGroupIdColumnIndex = mCursor.getColumnIndex(CatalogueDBAdapter.KEY_ROWID); return mCursor; } @Override public int getSectionNameColum() { return mCursor.getColumnIndex(CatalogueDBAdapter.KEY_AUTHOR_FORMATTED); } } /* * ViewManager for sorting by Author */ private class AuthorFirstViewManager extends ViewManager { AuthorFirstViewManager() { mLayout = R.layout.row_authors; mChildLayout = R.layout.row_authors_books; mFrom = new String[]{CatalogueDBAdapter.KEY_AUTHOR_FORMATTED_GIVEN_FIRST}; mTo = new int[]{R.id.row_family}; } public SQLiteCursor getChildrenCursor(Cursor groupCursor) { return mDbHelper.fetchAllBooksByAuthor(groupCursor.getInt(mGroupIdColumnIndex), bookshelf, search_query, false); } @Override public Cursor newGroupCursor() { if (search_query.equals("")) { // Return all books for the given bookshelf mCursor = mDbHelper.fetchAllAuthors(bookshelf, false, false); } else { // Return the search results instead of all books (for the bookshelf) mCursor = mDbHelper.searchAuthors(search_query, bookshelf, false, false); } mGroupIdColumnIndex = mCursor.getColumnIndex(CatalogueDBAdapter.KEY_ROWID); return mCursor; } @Override public int getSectionNameColum() { return mCursor.getColumnIndex(CatalogueDBAdapter.KEY_AUTHOR_FORMATTED_GIVEN_FIRST); } } /* * ViewManager for sorting by Author */ private class AuthorOneViewManager extends ViewManager { AuthorOneViewManager() { mLayout = R.layout.row_authors; mChildLayout = R.layout.row_authors_books; mFrom = new String[]{CatalogueDBAdapter.KEY_AUTHOR_FORMATTED}; mTo = new int[]{R.id.row_family}; } public SQLiteCursor getChildrenCursor(Cursor groupCursor) { return mDbHelper.fetchAllBooksByAuthor(groupCursor.getInt(mGroupIdColumnIndex), bookshelf, search_query, true); } @Override public Cursor newGroupCursor() { if (search_query.equals("")) { // Return all books for the given bookshelf mCursor = mDbHelper.fetchAllAuthors(bookshelf, true, true); } else { // Return the search results instead of all books (for the bookshelf) mCursor = mDbHelper.searchAuthors(search_query, bookshelf, true, true); } mGroupIdColumnIndex = mCursor.getColumnIndex(CatalogueDBAdapter.KEY_ROWID); return mCursor; } @Override public int getSectionNameColum() { return mCursor.getColumnIndex(CatalogueDBAdapter.KEY_AUTHOR_FORMATTED); } } /* * ViewManager for sorting by Series */ private class SeriesViewManager extends ViewManager { SeriesViewManager() { mLayout = R.layout.row_authors; mChildLayout = R.layout.row_series_books; mFrom = new String[]{CatalogueDBAdapter.KEY_SERIES_NAME}; mTo = new int[]{R.id.row_family}; } public SQLiteCursor getChildrenCursor(Cursor groupCursor) { return mDbHelper.fetchAllBooksBySeries(groupCursor.getString(groupCursor.getColumnIndex(CatalogueDBAdapter.KEY_SERIES_NAME)), bookshelf, search_query); } @Override public Cursor newGroupCursor() { if (search_query.equals("")) { mCursor = mDbHelper.fetchAllSeries(bookshelf, true); } else { mCursor = mDbHelper.searchSeries(search_query, bookshelf); } BookCatalogueClassic.this.startManagingCursor(mCursor); mGroupIdColumnIndex = mCursor.getColumnIndex(CatalogueDBAdapter.KEY_ROWID); return mCursor; } @Override public int getSectionNameColum() { return mCursor.getColumnIndex(CatalogueDBAdapter.KEY_SERIES_NAME); } } /* * ViewManager for sorting by Loan status */ private class LoanViewManager extends ViewManager { LoanViewManager() { mLayout = R.layout.row_authors; mChildLayout = R.layout.row_series_books; mFrom = new String[]{CatalogueDBAdapter.KEY_ROWID}; mTo = new int[]{R.id.row_family}; } public SQLiteCursor getChildrenCursor(Cursor groupCursor) { return mDbHelper.fetchAllBooksByLoan(groupCursor.getString(mGroupIdColumnIndex), search_query); } @Override public Cursor newGroupCursor() { mCursor = mDbHelper.fetchAllLoans(); mGroupIdColumnIndex = mCursor.getColumnIndex(CatalogueDBAdapter.KEY_ROWID); return mCursor; } @Override public int getSectionNameColum() { return mCursor.getColumnIndex(CatalogueDBAdapter.KEY_ROWID); } } /* * ViewManager for sorting by Unread */ private class UnreadViewManager extends ViewManager { UnreadViewManager() { mLayout = R.layout.row_authors; mChildLayout = R.layout.row_series_books; mFrom = new String[]{CatalogueDBAdapter.KEY_ROWID}; mTo = new int[]{R.id.row_family}; } public SQLiteCursor getChildrenCursor(Cursor groupCursor) { return mDbHelper.fetchAllBooksByRead(groupCursor.getString(mGroupIdColumnIndex), bookshelf, search_query); } @Override public Cursor newGroupCursor() { mCursor = mDbHelper.fetchAllUnreadPsuedo(); mGroupIdColumnIndex = mCursor.getColumnIndex(CatalogueDBAdapter.KEY_ROWID); return mCursor; } @Override public int getSectionNameColum() { return mCursor.getColumnIndex(CatalogueDBAdapter.KEY_ROWID); } } /* * ViewManager for sorting by Genre */ private class GenreViewManager extends ViewManager { GenreViewManager() { mLayout = R.layout.row_authors; mChildLayout = R.layout.row_books; mFrom = new String[]{CatalogueDBAdapter.KEY_ROWID}; mTo = new int[]{R.id.row_family}; } public SQLiteCursor getChildrenCursor(Cursor groupCursor) { if (search_query.equals("")) { return mDbHelper.fetchAllBooksByGenre(groupCursor.getString(mGroupIdColumnIndex), bookshelf, ""); } else { return mDbHelper.searchBooksByGenre(search_query, groupCursor.getString(mGroupIdColumnIndex), bookshelf); } } @Override public Cursor newGroupCursor() { if (search_query.equals("")) { // Return all books (for the bookshelf) mCursor = mDbHelper.fetchAllGenres(bookshelf); } else { // Return the search results instead of all books (for the bookshelf) mCursor = mDbHelper.searchGenres(search_query, bookshelf); } mGroupIdColumnIndex = mCursor.getColumnIndex(CatalogueDBAdapter.KEY_ROWID); return mCursor; } @Override public int getSectionNameColum() { return mCursor.getColumnIndex(CatalogueDBAdapter.KEY_ROWID); } } /* * ViewManager for sorting by Genre */ private class PublishedViewManager extends ViewManager { PublishedViewManager() { mLayout = R.layout.row_authors; mChildLayout = R.layout.row_books; mFrom = new String[]{CatalogueDBAdapter.KEY_ROWID}; mTo = new int[]{R.id.row_family}; } public SQLiteCursor getChildrenCursor(Cursor groupCursor) { if (search_query.equals("")) { return mDbHelper.fetchAllBooksByDatePublished(groupCursor.getString(mGroupIdColumnIndex), bookshelf, ""); } else { return mDbHelper.searchBooksByDatePublished(search_query, groupCursor.getString(mGroupIdColumnIndex), bookshelf); } } @Override public Cursor newGroupCursor() { if (search_query.equals("")) { // Return all books (for the bookshelf) mCursor = mDbHelper.fetchAllDatePublished(bookshelf); } else { // Return the search results instead of all books (for the bookshelf) mCursor = mDbHelper.searchDatePublished(search_query, bookshelf); } mGroupIdColumnIndex = mCursor.getColumnIndex(CatalogueDBAdapter.KEY_ROWID); return mCursor; } @Override public int getSectionNameColum() { return mCursor.getColumnIndex(CatalogueDBAdapter.KEY_ROWID); } } /** * Build the tree view */ private void fillData() { //check and reset mDbHelper. Avoid leaking cursors. Each one is 1MB (allegedly)! Cursor c = null; try { c = mDbHelper.fetchAllAuthors(bookshelf); } catch (NullPointerException e) { //reset mDbHelper = new CatalogueDBAdapter(this); mDbHelper.open(); } finally { if (c != null) c.close(); } ViewManager vm; /** * Select between the different ViewManager objects based on the sort parameter */ switch(sort) { case SORT_TITLE: vm = new TitleViewManager(); break; case SORT_AUTHOR: vm = new AuthorViewManager(); break; case SORT_AUTHOR_GIVEN: vm = new AuthorFirstViewManager(); break; case SORT_AUTHOR_ONE: vm = new AuthorOneViewManager(); break; case SORT_SERIES: vm = new SeriesViewManager(); break; case SORT_LOAN: vm = new LoanViewManager(); break; case SORT_UNREAD: vm = new UnreadViewManager(); break; case SORT_GENRE: vm = new GenreViewManager(); break; case SORT_PUBLISHED: vm = new PublishedViewManager(); break; default: throw new IllegalArgumentException(); } // Manage it startManagingCursor(vm.getCursor()); // Set view title if (search_query.equals("")) { this.setTitle(R.string.app_name); } else { int numResults = vm.getCursor().getCount(); Toast.makeText(this, numResults + " " + this.getResources().getString(R.string.results_found), Toast.LENGTH_LONG).show(); this.setTitle(getResources().getString(R.string.search_title) + " - " + search_query); } // Instantiate the List Adapter ViewManager.BasicBookListAdapter adapter = vm.newAdapter(this); // Handle the click event. Do not open, but goto the book edit page ExpandableListView expandableList = getExpandableListView(); // Extend the onGroupClick (Open) - Every click should add to the currentGroup array expandableList.setOnGroupExpandListener(new OnGroupExpandListener() { @Override public void onGroupExpand(int groupPosition) { if (mLoadingGroups == 0) adjustCurrentGroup(groupPosition, 1, false, false); } }); // Extend the onGroupClick (Close) - Every click should remove from the currentGroup array expandableList.setOnGroupCollapseListener(new OnGroupCollapseListener() { @Override public void onGroupCollapse(int groupPosition) { if (mLoadingGroups == 0) adjustCurrentGroup(groupPosition, -1, false, false); } }); /* Hide the default expandable icon, and use a different icon (actually the same icon) * The override is for when changing back from the title view and it has hidden the icon. */ Drawable indicator = this.getResources().getDrawable(R.drawable.expander_group); expandableList.setGroupIndicator(indicator); setListAdapter(adapter); // Force a rebuild of the fast scroller adapterChanged(); adapter.notifyDataSetChanged(); gotoCurrentGroup(); /* Add number to bookshelf */ TextView mBookshelfNumView = (TextView) findViewById(R.id.bookshelf_num); try { int numBooks = mDbHelper.countBooks(bookshelf); mBookshelfNumView.setText("(" + numBooks + ")"); } catch (IllegalStateException e) { Logger.logError(e); } } /** * Setup the sort options. This function will also call fillData when * complete having loaded the appropriate view. */ private void sortOptions() { ScrollView sv = new ScrollView(this); RadioGroup group = new RadioGroup(this); sv.addView(group); final AlertDialog sortDialog = new AlertDialog.Builder(this).setView(sv).create(); sortDialog.setTitle(R.string.menu_sort_by); sortDialog.setIcon(android.R.drawable.ic_menu_info_details); sortDialog.show(); RadioButton radio_author = new RadioButton(this); radio_author.setText(R.string.sortby_author); group.addView(radio_author); if (sort == SORT_AUTHOR) { radio_author.setChecked(true); } else { radio_author.setChecked(false); } radio_author.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { saveSortBy(SORT_AUTHOR); sortDialog.dismiss(); return; } }); RadioButton radio_author_one = new RadioButton(this); radio_author_one.setText(R.string.sortby_author_one); group.addView(radio_author_one); if (sort == SORT_AUTHOR_ONE) { radio_author_one.setChecked(true); } else { radio_author_one.setChecked(false); } radio_author_one.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { saveSortBy(SORT_AUTHOR_ONE); sortDialog.dismiss(); return; } }); RadioButton radio_author_given = new RadioButton(this); radio_author_given.setText(R.string.sortby_author_given); group.addView(radio_author_given); if (sort == SORT_AUTHOR_GIVEN) { radio_author_given.setChecked(true); } else { radio_author_given.setChecked(false); } radio_author_given.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { saveSortBy(SORT_AUTHOR_GIVEN); sortDialog.dismiss(); return; } }); RadioButton radio_title = new RadioButton(this); radio_title.setText(R.string.sortby_title); group.addView(radio_title); if (sort == SORT_TITLE) { radio_title.setChecked(true); } else { radio_title.setChecked(false); } radio_title.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { saveSortBy(SORT_TITLE); sortDialog.dismiss(); return; } }); RadioButton radio_series = new RadioButton(this); radio_series.setText(R.string.sortby_series); group.addView(radio_series); if (sort == SORT_SERIES) { radio_series.setChecked(true); } else { radio_series.setChecked(false); } radio_series.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { saveSortBy(SORT_SERIES); sortDialog.dismiss(); return; } }); RadioButton radio_genre = new RadioButton(this); radio_genre.setText(R.string.sortby_genre); group.addView(radio_genre); if (sort == SORT_GENRE) { radio_genre.setChecked(true); } else { radio_genre.setChecked(false); } radio_genre.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { saveSortBy(SORT_GENRE); sortDialog.dismiss(); return; } }); RadioButton radio_loan = new RadioButton(this); radio_loan.setText(R.string.sortby_loan); group.addView(radio_loan); if (sort == SORT_LOAN) { radio_loan.setChecked(true); } else { radio_loan.setChecked(false); } radio_loan.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { saveSortBy(SORT_LOAN); sortDialog.dismiss(); return; } }); RadioButton radio_unread = new RadioButton(this); radio_unread.setText(R.string.sortby_unread); group.addView(radio_unread); if (sort == SORT_UNREAD) { radio_unread.setChecked(true); } else { radio_unread.setChecked(false); } radio_unread.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { saveSortBy(SORT_UNREAD); sortDialog.dismiss(); return; } }); RadioButton radio_published = new RadioButton(this); radio_published.setText(R.string.sortby_published); group.addView(radio_published); if (sort == SORT_PUBLISHED) { radio_published.setChecked(true); } else { radio_published.setChecked(false); } radio_published.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { saveSortBy(SORT_PUBLISHED); sortDialog.dismiss(); return; } }); } private MenuHandler mMenuHandler; /** * Run each time the menu button is pressed. This will setup the options menu */ @Override public boolean onPrepareOptionsMenu(Menu menu) { mMenuHandler = new MenuHandler(); // RELEASE: RE-ENABLE THESE! // mMenuHandler.init(menu); // mMenuHandler.addCreateBookItems(menu); // // if (collapsed == true || currentGroup.size() == 0) { // mMenuHandler.addItem(menu, SORT_BY_AUTHOR_COLLAPSED, R.string.menu_sort_by_author_expanded, R.drawable.ic_menu_expand); // } else { // mMenuHandler.addItem(menu, SORT_BY_AUTHOR_EXPANDED, R.string.menu_sort_by_author_collapsed, R.drawable.ic_menu_collapse); // } // mMenuHandler.addItem(menu, SORT_BY, R.string.menu_sort_by, android.R.drawable.ic_menu_sort_alphabetically); // // mMenuHandler.addCreateHelpAndAdminItems(menu); // mMenuHandler.addSearchItem(menu); return super.onPrepareOptionsMenu(menu); } /** * This will be called when a menu item is selected. A large switch statement to * call the appropriate functions (or other activities) */ @Override public boolean onMenuItemSelected(int featureId, MenuItem item) { // MenuHandler handles the 'standard' items, we just handle local items. // if (mMenuHandler == null || !mMenuHandler.onMenuItemSelected(this, featureId, item)) { // switch(item.getItemId()) { // case SORT_BY_AUTHOR_COLLAPSED: // expandAll(); // return true; // case SORT_BY_AUTHOR_EXPANDED: // collapseAll(); // return true; // case SORT_BY: // sortOptions(); // return true; // } // } return super.onMenuItemSelected(featureId, item); } /* * Save Current group to preferences */ public void saveCurrentGroup() { try { SharedPreferences.Editor ed = mPrefs.edit(); ed.putInt(STATE_CURRENT_GROUP_COUNT, currentGroup.size()); int i = 0; Iterator<Integer> arrayIterator = currentGroup.iterator(); while(arrayIterator.hasNext()) { int currentValue = arrayIterator.next(); ed.putInt(STATE_CURRENT_GROUP + " " + i, currentValue); i++; } ed.commit(); } catch (Exception e) { Logger.logError(e); } return; } /* * Load Current group from preferences */ public void loadCurrentGroup() { try { if (currentGroup != null) currentGroup.clear(); else currentGroup = new ArrayList<Integer>(); int count = mPrefs.getInt(STATE_CURRENT_GROUP_COUNT, -1); int i = 0; while(i < count) { int pos = mPrefs.getInt(STATE_CURRENT_GROUP + " " + i, -1); if (pos >= 0) { adjustCurrentGroup(pos, 1, true, false); } i++; } if (count == 0) collapsed = true; } catch (Exception e) { Logger.logError(e); } return; } /** * Expand and scroll to the current group */ public void gotoCurrentGroup() { try { synchronized(mLoadingGroups) { mLoadingGroups += 1; } // DEBUG: //System.gc(); //Debug.MemoryInfo before = new Debug.MemoryInfo(); //Debug.getMemoryInfo(before); //long t0 = System.currentTimeMillis(); ExpandableListView view = this.getExpandableListView(); ArrayList<Integer> localCurrentGroup = currentGroup; Iterator<Integer> arrayIterator = localCurrentGroup.iterator(); while(arrayIterator.hasNext()) { view.expandGroup(arrayIterator.next()); //System.out.println("Cursor count: " + TrackedCursor.getCursorCountApproximate()); } // DEBUG: //Debug.MemoryInfo after = new Debug.MemoryInfo(); //t0 = System.currentTimeMillis() - t0; //System.gc(); //Debug.getMemoryInfo(after); // //int delta = (after.dalvikPrivateDirty + after.nativePrivateDirty + after.otherPrivateDirty) // - (before.dalvikPrivateDirty + before.nativePrivateDirty + before.otherPrivateDirty); //System.out.println("Usage Change = " + delta + " (completed in " + t0 + "ms)"); int pos = localCurrentGroup.size()-1; if (pos >= 0) { view.setSelectedGroup(localCurrentGroup.get(pos)); } } catch (NoSuchFieldError e) { //do nothing } catch (Exception e) { Logger.logError(e); } finally { synchronized(mLoadingGroups) { mLoadingGroups -= 1; } } return; } /** * add / remove items from the current group arrayList * * @param pos The position to add or remove * @param adj Adjustment to make (+1/-1 = open/close) * @param force If force is true, then it will be always be added (if adj=1), even if it already exists - but moved to the end */ public void adjustCurrentGroup(int pos, int adj, boolean force, boolean save) { int index = currentGroup.indexOf(pos); if (index == -1) { //it does not exist (so is not open), so if adj=1, add to the list if (adj > 0) { currentGroup.add(pos); /* Add the latest position to the preferences */ } } else { //it does exist (so is open), so remove from the list if adj=-1 if (adj < 0) { currentGroup.remove(index); } else { if (force == true) { currentGroup.remove(index); currentGroup.add(pos); /* Add the latest position to the preferences */ } } } collapsed = (currentGroup.size() == 0); if (save) saveCurrentGroup(); } /** * Expand all Groups */ public void expandAll() { ExpandableListView view = this.getExpandableListView(); ExpandableListAdapter ad = view.getExpandableListAdapter(); int numAuthors = ad.getGroupCount(); currentGroup = new ArrayList<Integer>(); int i = 0; while (i < numAuthors) { adjustCurrentGroup(i, 1, false, false); view.expandGroup(i); i++; } collapsed = false; } /** * Collapse all Author Groups. Passes directly to collapseAll(boolean clearCurrent) * * @see collapseAll(boolean clearCurrent) */ public void collapseAll() { collapseAll(true); } /** * Collapse all Author Groups * * @param clearCurrent - Also clear the currentGroup ArrayList */ public void collapseAll(boolean clearCurrent) { // there is no current group anymore ExpandableListView view = this.getExpandableListView(); ExpandableListAdapter ad = view.getExpandableListAdapter(); int numAuthors = ad.getGroupCount(); int i = 0; while (i < numAuthors) { view.collapseGroup(i); //if (!expand) { // break; //} i++; } if (clearCurrent) { currentGroup = new ArrayList<Integer>(); } collapsed = true; } @Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); ExpandableListView.ExpandableListContextMenuInfo info = (ExpandableListView.ExpandableListContextMenuInfo) menuInfo; try { // Only delete titles, not authors if (ExpandableListView.getPackedPositionType(info.packedPosition) == ExpandableListView.PACKED_POSITION_TYPE_CHILD) { MenuItem delete = menu.add(0, DELETE_ID, 0, R.string.menu_delete); delete.setIcon(android.R.drawable.ic_menu_delete); MenuItem edit_book = menu.add(0, EDIT_BOOK, 0, R.string.edit_book); edit_book.setIcon(android.R.drawable.ic_menu_edit); MenuItem edit_book_notes = menu.add(0, EDIT_BOOK_NOTES, 0, R.string.edit_book_notes); edit_book_notes.setIcon(R.drawable.ic_menu_compose); MenuItem edit_book_friends = menu.add(0, EDIT_BOOK_FRIENDS, 0, R.string.edit_book_friends); edit_book_friends.setIcon(R.drawable.ic_menu_cc); // Send book to goodreads MenuItem edit_book_send_to_gr = menu.add(0, EDIT_BOOK_SEND_TO_GR, 0, R.string.edit_book_send_to_gr); edit_book_send_to_gr.setIcon(R.drawable.ic_menu_cc); } else if (ExpandableListView.getPackedPositionType(info.packedPosition) == ExpandableListView.PACKED_POSITION_TYPE_GROUP) { switch(sort) { case SORT_AUTHOR: case SORT_AUTHOR_GIVEN: { MenuItem edit_book = menu.add(0, EDIT_AUTHOR_ID, 0, R.string.menu_edit_author); edit_book.setIcon(android.R.drawable.ic_menu_edit); break; } case SORT_SERIES: { MenuItem delete = menu.add(0, DELETE_SERIES_ID, 0, R.string.menu_delete_series); delete.setIcon(android.R.drawable.ic_menu_delete); MenuItem edit_book = menu.add(0, EDIT_SERIES_ID, 0, R.string.menu_edit_series); edit_book.setIcon(android.R.drawable.ic_menu_edit); break; } } } } catch (NullPointerException e) { Logger.logError(e); } } @Override public boolean onContextItemSelected(MenuItem item) { ExpandableListContextMenuInfo info = (ExpandableListContextMenuInfo) item.getMenuInfo(); switch(item.getItemId()) { case DELETE_ID: int res = StandardDialogs.deleteBookAlert(this, mDbHelper, info.id, new Runnable() { @Override public void run() { mDbHelper.purgeAuthors(); mDbHelper.purgeSeries(); fillData(); }}); if (res != 0) Toast.makeText(this, res, Toast.LENGTH_LONG).show(); return true; case EDIT_BOOK: BookEdit.editBook(this, info.id, BookEdit.TAB_EDIT); return true; case EDIT_BOOK_NOTES: BookEdit.editBook(this, info.id, BookEdit.TAB_EDIT_NOTES); return true; case EDIT_BOOK_FRIENDS: BookEdit.editBook(this, info.id, BookEdit.TAB_EDIT_FRIENDS); return true; case EDIT_BOOK_SEND_TO_GR: // Get a GoodreadsManager and make sure we are authorized. GoodreadsManager grMgr = new GoodreadsManager(); if (!grMgr.hasValidCredentials()) { try { grMgr.requestAuthorization(this); } catch (NetworkException e) { Toast.makeText(this, e.getLocalizedMessage(), Toast.LENGTH_LONG).show(); } } // get a QueueManager and queue the task. QueueManager qm = BookCatalogueApp.getQueueManager(); SendOneBookTask task = new SendOneBookTask(info.id); qm.enqueueTask(task, BcQueueManager.QUEUE_MAIN, 0); return true; case EDIT_SERIES_ID: { if (info.id==-1) { Toast.makeText(this, R.string.cannot_edit_system, Toast.LENGTH_LONG).show(); } else { Series s = mDbHelper.getSeriesById(info.id); EditSeriesDialog d = new EditSeriesDialog(this, mDbHelper, new Runnable() { @Override public void run() { mDbHelper.purgeSeries(); regenGroups(); }}); d.editSeries(s); } break; } case DELETE_SERIES_ID: { StandardDialogs.deleteSeriesAlert(this, mDbHelper, mDbHelper.getSeriesById(info.id), new Runnable() { @Override public void run() { regenGroups(); }}); break; } case EDIT_AUTHOR_ID: { EditAuthorDialog d = new EditAuthorDialog(this, mDbHelper, new Runnable() { @Override public void run() { mDbHelper.purgeAuthors(); regenGroups(); }}); d.editAuthor(mDbHelper.getAuthorById(info.id)); break; } } return super.onContextItemSelected(item); } /** * Utility routine to regenerate the groups cursor. */ private void regenGroups() { ViewManager.BasicBookListAdapter adapter = (ViewManager.BasicBookListAdapter) getExpandableListAdapter(); adapter.regenGroups(); } /** * Change the sort order of the view and refresh the page */ private void saveSortBy(int sortType) { sort = sortType; currentGroup = new ArrayList<Integer>(); fillData(); /* Save the current sort settings */ SharedPreferences.Editor ed = mPrefs.edit(); ed.putInt(STATE_SORT, sortType); ed.commit(); } @Override public boolean onChildClick(ExpandableListView l, View v, int position, int childPosition, long id) { boolean result = super.onChildClick(l, v, position, childPosition, id); adjustCurrentGroup(position, 1, true, false); BookEdit.openBook(this, id, null, null); return result; } /** * Called when an activity launched exits, giving you the requestCode you started it with, * the resultCode it returned, and any additional data from it. */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent intent) { super.onActivityResult(requestCode, resultCode, intent); switch(requestCode) { case UniqueId.ACTIVITY_CREATE_BOOK_SCAN: try { String contents = intent.getStringExtra("SCAN_RESULT"); // Handle the possibility of null/empty scanned string if (contents != null && !contents.equals("")) { Toast.makeText(this, R.string.isbn_found, Toast.LENGTH_LONG).show(); Intent i = new Intent(this, BookISBNSearch.class); i.putExtra("isbn", contents); startActivityForResult(i, UniqueId.ACTIVITY_CREATE_BOOK_SCAN); } else { fillData(); } } catch (NullPointerException e) { // This is not a scan result, but a normal return fillData(); } break; case UniqueId.ACTIVITY_CREATE_BOOK_ISBN: case UniqueId.ACTIVITY_CREATE_BOOK_MANUALLY: case UniqueId.ACTIVITY_EDIT_BOOK: case UniqueId.ACTIVITY_SORT: case UniqueId.ACTIVITY_ADMIN: try { // Use the ADDED_* fields if present. if (intent != null && intent.hasExtra(BookEdit.ADDED_HAS_INFO)) { if (sort == SORT_TITLE) { justAdded = intent.getStringExtra(BookEdit.ADDED_TITLE); int position = mDbHelper.fetchBookPositionByTitle(justAdded, bookshelf); adjustCurrentGroup(position, 1, true, false); } else if (sort == SORT_AUTHOR) { justAdded = intent.getStringExtra(BookEdit.ADDED_AUTHOR); int position = mDbHelper.fetchAuthorPositionByName(justAdded, bookshelf); adjustCurrentGroup(position, 1, true, false); } else if (sort == SORT_AUTHOR_GIVEN) { justAdded = intent.getStringExtra(BookEdit.ADDED_AUTHOR); int position = mDbHelper.fetchAuthorPositionByGivenName(justAdded, bookshelf); adjustCurrentGroup(position, 1, true, false); } else if (sort == SORT_SERIES) { justAdded = intent.getStringExtra(BookEdit.ADDED_SERIES); int position = mDbHelper.fetchSeriesPositionBySeries(justAdded, bookshelf); adjustCurrentGroup(position, 1, true, false); } else if (sort == SORT_GENRE) { justAdded = intent.getStringExtra(BookEdit.ADDED_GENRE); int position = mDbHelper.fetchGenrePositionByGenre(justAdded, bookshelf); adjustCurrentGroup(position, 1, true, false); } } } catch (Exception e) { Logger.logError(e); } // We call bookshelf not fillData in case the bookshelves have been updated. bookshelf(); break; case UniqueId.ACTIVITY_ADMIN_FINISH: finish(); break; } } /** * Restore UI state when loaded. */ @Override public void onResume() { try { mPrefs = getSharedPreferences("bookCatalogue", MODE_PRIVATE); sort = mPrefs.getInt(STATE_SORT, sort); bookshelf = mPrefs.getString(BooksOnBookshelf.PREF_BOOKSHELF, bookshelf); loadCurrentGroup(); } catch (Exception e) { Logger.logError(e); } super.onResume(); } /** * Save UI state changes. */ @Override public void onPause() { saveCurrentGroup(); SharedPreferences.Editor ed = mPrefs.edit(); ed.putInt(STATE_SORT, sort); ed.putString(BooksOnBookshelf.PREF_BOOKSHELF, bookshelf); ed.commit(); saveCurrentGroup(); super.onPause(); } @Override protected void onDestroy() { try { destroyManagedCursors(); if (mDbHelper != null) { mDbHelper.close(); mDbHelper = null; } } catch (RuntimeException e) { // could not be closed (app crash maybe). Don't worry about it } if (mTaskQueue != null) { try { mTaskQueue.finish(); } catch (Exception e) {}; mTaskQueue = null; } if (mUtils != null) { try { mUtils.close(); } catch (Exception e) {}; mUtils = null; } super.onDestroy(); } //@Override //public boolean onKeyDown(int keyCode, KeyEvent event) { // if (keyCode == KeyEvent.KEYCODE_BACK) { // if (search_query.equals("")) { // int opened = mPrefs.getInt(STATE_OPENED, BACKUP_PROMPT_WAIT); // SharedPreferences.Editor ed = mPrefs.edit(); // if (opened == 0){ // ed.putInt(STATE_OPENED, BACKUP_PROMPT_WAIT); // ed.commit(); // BookCatalogueApp.backupPopup(this); // return true; // } else { // ed.putInt(STATE_OPENED, opened - 1); // ed.commit(); // } // } // } // return super.onKeyDown(keyCode, event); //} /** * When the adapter is changed, we need to rebuild the FastScroller. */ public void adapterChanged() { // Reset the fast scroller FastScrollExpandableListView lv = (FastScrollExpandableListView)this.getExpandableListView(); lv.setFastScrollEnabled(false); lv.setFastScrollEnabled(true); } /** * Accessor used by Robotium test harness. * * @param s New search string. */ public void setSearchQuery(String s) { search_query = s; regenGroups(); } }